/*
 * Copyright 2011 Marcin Blazejewski
 *
 * This file is part of Ground0.
 *
 * Ground0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ground0 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ground0. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>

#include "stuff.hpp"
#include "eye.hpp"
#include "geometry.hpp"

#include <GL/gl.h>
#include <GL/glu.h>
#ifdef WIN32
#include <GL/glext.h>
#endif
#include <SDL/SDL.h>
#include <string.h>
#include <math.h>
#include <iostream>

using namespace std;


Block::Block() {
    ready = false;
}

Block::~Block() {
    for (int i = 0; i < vertices.size(); i++)
        delete vertices[i];

    for (int i = 0; i < normals.size(); i++)
        delete normals[i];

    for (int i = 0; i < texcoords.size(); i++)
        delete texcoords[i];

}

void Block::calc_normals() {
    int i, j;
    
    for (i = 0; i < faces.size(); i++) {
        for (j = 0; j < faces[i].size(); j++) {

            Face &f = faces[i][j];

            if ((f.normals[0] == NULL) || (f.normals[1] == NULL) || (f.normals[2] == NULL)) {
                Vector *n = new Vector(face_normal( *f.vertices[0],
                                                    *f.vertices[1],
                                                    *f.vertices[2] ));
                                      
                normals.push_back(n);
                
                if (f.normals[0] == NULL) f.normals[0] = n;
                if (f.normals[1] == NULL) f.normals[1] = n;
                if (f.normals[2] == NULL) f.normals[2] = n;
            }
        }
    }
     
    for (i = 0; i < normals.size(); i++) {
        normals[i]->normalize();
    }

}

void Block::prepare() {
    vector<Face>::iterator fi;    // face iterator

    for (int i = 0; i < faces.size(); i++)
        disp_lists.push_back( glGenLists(1) );

    // iterate all face sets
    for (int i = 0; i < faces.size(); i++) {
        
        // Prepare a display list for each face set.
        // To render, each list will be called with its material set.
        glNewList(disp_lists[i], GL_COMPILE);
        glBegin(GL_TRIANGLES);
        for (fi = faces[i].begin(); fi != faces[i].end(); fi++) {
            
            for (int vi = 0; vi < 3; vi++) {
                Vector &v = *fi->vertices[vi];
                Vector &n = *fi->normals[vi];
                if (fi->texcoords[vi] != NULL) {
                    Vector *t = fi->texcoords[vi];
                    glTexCoord2f( t->x, t->y );
                }
                glNormal3f( n.x, n.y, n.z );
                glVertex3f( v.x, v.y, v.z );
            }
        }

        glEnd();
        glEndList();
    }

    ready = true;
}

void Block::draw() {
    if (!ready) prepare();

    vector<GLuint>::iterator li;
    vector<Material*>::iterator mi;

    for (li = disp_lists.begin(), mi = materials.begin(); li != disp_lists.end(); li++, mi++) {
        if (*mi) (*mi)->apply();
        glCallList(*li);
    }
}

ostream& operator<<(ostream &o, const Block &b) {
    o << "# vertices" << endl;
    for (int i = 0; i < b.vertices.size(); i++) {
        o << *b.vertices[i] << " at " << b.vertices[i] << endl;
    }

    o << "# normals" << endl;
    for (int i = 0; i < b.normals.size(); i++) {
        o << *b.normals[i] << endl;
    }

    for (int j = 0; j < b.faces.size(); j++) {
        o << "# face set " << j << endl;
        for (int i = 0; i < b.faces[j].size(); i++) {
            o << b.faces[j][i] << endl;
        }
    }

    return o;
}



/*******************************************************************************/
/* BlockBuilder */
/*******************************************************************************/



BlockBuilder::BlockBuilder(Block *_b) { 
    if (_b == NULL) 
        block = new Block();
    else 
        block = _b;
    current_faceset = -1;
}

void BlockBuilder::add_vertex(Vector &v) {
    block->vertices.push_back(new Vector(v));
}

void BlockBuilder::add_normal(Vector &n ) {
    block->normals.push_back(new Vector(n));
}

void BlockBuilder::add_texcoord(Vector &t) {
    block->texcoords.push_back(new Vector(t));
}

int BlockBuilder::add_face_set(Material *m) {
    vector<Face> empty;
    block->faces.push_back(empty);
    block->materials.push_back(m);

    current_faceset = block->faces.size() - 1;
    return current_faceset;
}

void BlockBuilder::add_face(int faceset, const Face &f) {
    // These pointers will be used to create the final face
    Vector *v[3] = { NULL, NULL, NULL }; // vertices
    Vector *n[3] = { NULL, NULL, NULL }; // normals
    Vector *t[3] = { NULL, NULL, NULL }; // tex coords

    // iterate each triangle vertex, normal and tex coord
    for (int i = 0; i < 3; i++) {

        // vertices

        // look for identical vertex in block's vertices array
        for (int j = 0; j < block->vertices.size(); j++) {
            if (*block->vertices[j] == *f.vertices[i]) v[i] = block->vertices[j];
        }
        
        // didn't find matching object in block's arrays, create a new one
        if (v[i] == NULL) {
            v[i] = new Vector(*f.vertices[i]);
            block->vertices.push_back( v[i] );
            
        }

        // normals

        // look for identical normal in block's normals array
        for (int j = 0; j < block->normals.size(); j++) {
            if (f.normals[i] != NULL)
                if (*block->normals[j] == *f.normals[i]) n[i] = block->normals[j];
        }
        
        // didn't find matching object in block's arrays, create a new one
        if ((n[i] == NULL) && (f.normals[i] != NULL)) {
            n[i] = new Vector(*f.normals[i]);
            block->normals.push_back( n[i] );
        }

        // texcoords

        // look for identical coord in block's texcoords array
        for (int j = 0; j < block->texcoords.size(); j++) {
            if (f.texcoords[i] != NULL) 
                if (*block->texcoords[j] == *f.texcoords[i]) t[i] = block->texcoords[j];
        }
        
        // didn't find matching object in block's arrays, create a new one
        if ((t[i] == NULL) && (f.texcoords[i] != NULL)) {
            t[i] = new Vector( *f.texcoords[i] );
            block->texcoords.push_back( t[i] );
        }
    }

    // done, now create the face object using the data in v,n,t arrays
    Face face(v[0], v[1], v[2]);
    memcpy(face.normals, n, sizeof(n));
    memcpy(face.texcoords, t, sizeof(t));
    // add the face to the face set
    block->faces[faceset].push_back(face);
}

void BlockBuilder::add_face(const Face &f) {
    if (current_faceset < 0) throw string("no face sets");
    add_face(current_faceset, f);
}


/*******************************************************************************/
/* Light */
/*******************************************************************************/


Light::Light(float v) {
    on = (v != 0.0f);
    memset(&pos, 0, sizeof(pos));
    pos[3] = 1.0f;
    color[0] = color[1] = color[2] = v;
    color[3] = 1.0f;
}

void Light::move(float x, float y, float z) {
    pos[0] += x;
    pos[1] += y;
    pos[2] += z;
}

void Light::set_color(float r, float g, float b) {
    color[0] = r;
    color[1] = g;
    color[2] = b;
    on = ((r+g+b) != 0.0f);
}

/*******************************************************************************/
/* Material */
/*******************************************************************************/

Material::Material(float r, float g, float b) {
    memset(ambient, 0, sizeof(ambient));
    memset(specular, 0, sizeof(specular));
    diffuse[0] = r;
    diffuse[1] = g;
    diffuse[2] = b;
    diffuse[3] = 1.0;
    glGenTextures(1, &texture);
    use_tex = false;
}

Material::~Material() {
}

//#define USE_MIPMAP
void Material::set_texture(const char *texfile) {
    SDL_Surface *bmp = SDL_LoadBMP(texfile);
    if (!bmp) throw string("load texture image: ") + string(texfile);

    glBindTexture(GL_TEXTURE_2D, texture);

    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
#ifdef USE_MIPMAP    
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, bmp->w, bmp->h, GL_BGR, GL_UNSIGNED_BYTE, bmp->pixels);
#else
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    int fmt = GL_BGR;
    if (bmp->format->BytesPerPixel > 3) fmt = GL_BGRA;
    glTexImage2D(GL_TEXTURE_2D, 
                 0, /* lev of detail (base) */
                 3, /* num of color components in data (rgb) */
                 bmp->w, bmp->h, /* width, height */
                 0, /* border (no) */
                 fmt, /* data format */
                 GL_UNSIGNED_BYTE, /* data type */
                 bmp->pixels);
#endif    

    SDL_FreeSurface(bmp);
    use_tex = true;
}

void Material::apply() {
    if (use_tex) 
        glBindTexture(GL_TEXTURE_2D, texture);
    else
        glBindTexture(GL_TEXTURE_2D, 0);

    glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, diffuse);
}


/*******************************************************************************/
/* PartSet */
/*******************************************************************************/

PartSet::PartSet(float _decay_rate, int _new_life) {
    decay_rate = _decay_rate;
    new_life = _new_life;
}

PartSet::~PartSet() {
}

void PartSet::add_particle(int x0, int y0, int z0,
                           int vx, int vy, int vz,
                           int life) {
    if (life <= 0) life = new_life;
    Particle p = { x0, y0, z0, vx, vy, vz, life };
    particles.push_back(p);
}

void PartSet::tick(const float dt) {
    list<Particle>::iterator i;

    for (i=particles.begin(); i!=particles.end(); /*nop*/) {
        i->x += i->vx * dt;
        i->y += i->vy * dt;
        i->z += i->vz * dt;

        i->life -= (int)(decay_rate * dt);
        if (i->life <= 0) {
            i = particles.erase(i);
        } else {
            i++;
        }
    }
}

#define PSCALE -0.045f
void PartSet::draw() {
    list<Particle>::iterator i;

    for (i=particles.begin(); i!=particles.end(); i++) {
        glBegin(GL_LINES);
        glColor4f(0.9, 0.4, 0, 1);
        glVertex3f( i->x, i->y, i->z );

        glColor4f(0.4, 0.04, 0, 1);
        glVertex3f( i->x + i->vx*PSCALE, i->y + i->vy*PSCALE, i->z + i->vz*PSCALE );

        //glVertex3f( i->x - 0.2f, particles[i].y, particles[i].z );
        //glVertex3f( i->x, particles[i].y, particles[i].z + 1.0f );
        glEnd();
    }
}

